package cinvestment;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

import concurrent.Executer;
import support.AlgorithmSupport;
import util.Lang;
import util.MathUtil;

/**
 * Solve the problem based on proposition 2 in Van 2007 paper using subgradient
 * method
 * **/
public class CapacityInvestmentGradient {

	public CapacityInvestmentGradient(double delta, int productTypes,
			int simulationCount, double[] penalties, double c1) {
		this.delta = delta;
		this.N = productTypes;
		this.simulatedCount = simulationCount;
		this.p = Lang.clone(penalties);
		this.c1 = c1;
	}

	public void setStructure(String s) {
		this.structure = s;
	}

	private String structure = AlgorithmSupport.EXTENSIVE;

	private List<Plant> plants;
	private double[][] demands;

	/**
	 * Number of kinds of products
	 */
	private int N;

	/**
	 * Penalty cost for unfilled demand
	 * **/
	// private double p = 1.0;
	// try to vary the penalty cost of different products
	private double[] p;

	/**
	 * Number of plants
	 */
	private int n;

	/**
	 * Tolerance for the gap between Upper bound and lower bound
	 * **/
	private double epsilon = 0.00001;

	/**
	 * Basic cost for flexible resource cost
	 * **/
	private double c1;
	private double delta;

	/**
	 * Sampling count of demand
	 * **/
	private int simulatedCount;

	/**
	 * iteration counter
	 * **/
	private int iter;

	private double variance_demand = 0.3;

	public void setVarianceDemand(double value) {
		this.variance_demand = value;
	}

	private void init() {
		// this.demands = AlgorithmSupport.getDemands_uniform(simulatedCount, N,
		// 0.0, 2.0);
		this.demands = AlgorithmSupport.getDemands_normal(simulatedCount, N,
				1.0, this.variance_demand, 0.0, 2.0);
		// this.plants = AlgorithmSupport.createExtensivePlants(N);
		// this.plants = AlgorithmSupport.createWShapePlants(N);
		this.plants = AlgorithmSupport.createPlantsByStructure(N, structure);
		this.n = this.plants.size();
	}

	public List<Double[]> lshape() {
		this.init();
		List<Double[]> finalResult = new ArrayList<>();
		// iteration step size
		double t = BigDecimal.ONE.divide(BigDecimal.valueOf(n), 5,
				BigDecimal.ROUND_HALF_EVEN).doubleValue();

		double[] Kv = new double[n];
		for (int j = 0; j < n; j++)
			Kv[j] = BigDecimal
					.valueOf(N)
					.divide(BigDecimal.valueOf(n), 5,
							BigDecimal.ROUND_HALF_EVEN).doubleValue();
		double[] flexibleCosts = new double[n];
		for (int j = 0; j < n; j++)
			flexibleCosts[j] = this.getFlexibleCost(plants.get(j)
					.getAvailableProductsNumber());
		double[] Et = new double[n];

		double[] formerGradientU = new double[n];
		double[] fformerK = new double[n];
		double[] formerK = new double[n];

		iter = 1;
		while (true) {
			double[] gradientPi = new double[n];
			double[] estimatedGradU = new double[n];
			double estimatedObj = 0.0;
			for (int j = 0; j < n; j++)
				gradientPi[j] = 0.0;

			for (int j = 0; j < n; j++) {
				Et[j] = 0.0;
				estimatedGradU[j] = 0.0;
			}

			Executer executer = new Executer(100, true);
			for (int m = 0; m < simulatedCount; m++) {
				executer.fork(new DualSolver(N, n, demands[m], Kv, this.p,
						plants, m));
			}
			List<DualResult> dualResults = executer.join();
			executer.shutdown();

			int optimalDualCount = 0;
			for (DualResult r : dualResults) {
				if (r != null) {
					gradientPi = MathUtil.sum(gradientPi, r.getLambda());
					estimatedObj += r.getObjValue();
					optimalDualCount++;
				}
			}
			// unbiased estimate for expectation of pi
			for (int j = 0; j < gradientPi.length; j++) {
				gradientPi[j] = BigDecimal
						.valueOf(gradientPi[j])
						.divide(BigDecimal.valueOf(optimalDualCount), 5,
								BigDecimal.ROUND_HALF_EVEN).doubleValue();
			}
			estimatedObj = BigDecimal
					.valueOf(estimatedObj)
					.divide(BigDecimal.valueOf(optimalDualCount), 5,
							BigDecimal.ROUND_HALF_EVEN).doubleValue();

			double scaleG = 0.0;
			// c+gradient of pi
			for (int j = 0; j < n; j++) {
				estimatedGradU[j] += (BigDecimal.valueOf(flexibleCosts[j])
						.add(BigDecimal.valueOf(gradientPi[j]))).setScale(5,
						BigDecimal.ROUND_HALF_EVEN).doubleValue();
				scaleG += Kv[j] * estimatedGradU[j];
			}
			System.out.print("gradient U = ");
			Lang.print(estimatedGradU);
			double norm = MathUtil.twoNorm(estimatedGradU);
			// double norm = MathUtil.oneNorm(estimatedGradU);
			System.out.println("@check scale gradient = " + norm
					+ ", scaleG = " + scaleG);
			if (scaleG < epsilon) {
				for (int j = 0; j < n; j++)
					estimatedObj += flexibleCosts[j] * Kv[j];

				Double[] fobj = { estimatedObj };
				Double[] fcap = new Double[n];
				fcap = Lang.convertdouble2Double(Kv);
				finalResult.add(fobj);
				finalResult.add(fcap);

				break;
			} else {
				// double t = 1.0;
				// update t;
				double differenceK = MathUtil.twoNorm(MathUtil.substract(
						formerGradientU, estimatedGradU));
				System.out.println("@check difference of K = " + differenceK);
				if (iter > 2) {
					if (differenceK < 0.005)
						t = t * 2;
					else
						t = BigDecimal
								.valueOf(
										MathUtil.twoNorm(MathUtil.substract(
												formerK, fformerK)))
								.divide(BigDecimal.valueOf(MathUtil
										.twoNorm(estimatedGradU)), 5,
										BigDecimal.ROUND_HALF_EVEN)
								.doubleValue();
				}

				for (int i = 0; i < n; i++) {
					double ukv = BigDecimal
							.valueOf(Kv[i])
							.subtract(
									BigDecimal
											.valueOf(t)
											.multiply(
													BigDecimal
															.valueOf(estimatedGradU[i])))
							.setScale(5, BigDecimal.ROUND_HALF_EVEN)
							.doubleValue();
					Kv[i] = ukv > 0 ? ukv : 0.0;
				}

				formerGradientU = Lang.clone(estimatedGradU);
				fformerK = Lang.clone(formerK);
				formerK = Lang.clone(Kv);

				System.out.println("iter = " + iter + ", t = " + t
						+ "; updated Kv :");
				Lang.print(Kv);

				iter++;
			}
		}

		return finalResult;
	}

	/**
	 * Calculate the flexible cost for a plant based on the number of products
	 * it can produce. c1[1+(countj-1)*delta]
	 * 
	 * @param number
	 *            : the number of products the plant can produce
	 * **/
	private double getFlexibleCost(int number) {
		// linear cost in number
		return BigDecimal
				.valueOf(c1)
				.multiply(
						BigDecimal.valueOf(1).add(
								BigDecimal.valueOf(delta).multiply(
										BigDecimal.valueOf(number - 1))))
				.doubleValue();

		// // convex in number
		// return BigDecimal
		// .valueOf(c1)
		// .multiply(
		// BigDecimal.ONE.add(BigDecimal.valueOf(
		// Math.pow(number - 1, 1.5)).multiply(
		// BigDecimal.valueOf(delta)))).doubleValue();
	}
}
